Fiber 架构
React
更换架构的原因是为了解决 React15
和 React15
之前版本中渲染机制同步的问题,以及递归更新过程中无法中断的问题。
基础包结构
1。 react
react
基础包, 只提供定义 react
组件 (ReactElement)
的必要函数, 一般来说需要和渲染器 (react-dom,react-native)
一同使用。 在编写 react
应用的代码时, 大部分都是调用此包的 api
。
2。 react-dom
react
渲染器之一, 是 react
与 web
平台连接的桥梁(可以在浏览器和 nodejs 环境中使用), 将 react-reconciler
中的运行结果输出到 web
界面上。 在编写react
应用的代码时,大多数场景下, 能用到此包的就是一个入口函数 ReactDOM.render(<App/>, document.getElementById('root'))
, 其余使用的 api
, 基本是 react
包提供的。
3。 react-reconciler
react
得以运行的核心包(综合协调 react-dom
, react
, scheduler
各包之间的调用与配合)。
管理 react
应用状态的输入和结果的输出。 将输入信号最终转换成输出信号传递给渲染器。
- 接受输入
(scheduleUpdateOnFiber)
, 将fiber
树生成逻辑封装到一个回调函数中(涉及fiber
树形结构,fiber.updateQueue
队列, 调和算法等)。 - 把此回调函数(
performSyncWorkOnRoot或performConcurrentWorkOnRoot
)送入scheduler
进行调度。 scheduler
会控制回调函数执行的时机, 回调函数执行完成后得到全新的fiber
树。- 再调用渲染器(如
react-dom
,react-native
等)将fiber
树形结构最终反映到界面上。
4。 scheduler
调度机制的核心实现, 控制由 react-reconciler
送入的回调函数的执行时机, 在concurrent
模式下可以实现任务分片。 在编写 react
应用的代码时, 同样几乎不会直接用到此包提供的 api
。
- 核心任务就是执行回调(回调函数由
react-reconciler
提供)。 - 通过控制回调函数的执行时机, 来达到任务分片的目的, 实现可中断渲染(concurrent模式下才有此特性)。
架构分层
接口层(api)
内核层(core)
- 调度器
scheduler
包, 核心职责只有1
个, 就是执行回调。
- 把
react-reconciler
提供的回调函数, 包装到一个任务对象中。 - 在内部维护一个任务队列, 优先级高的排在最前面。
- 循环消费任务队列, 直到队列清空。
- 构造器
react-reconciler
包, 有3
个核心职责:- 装载渲染器, 渲染器必须实现
HostConfig协议
(如:react-dom
), 保证在需要的时候, 能够正确调用渲染器的api
, 生成实际节点(如:dom节点
)。 - 接收
react-dom
包(初次render
)和react
包(后续更新setState
)发起的更新请求。 - 将
fiber
树的构造过程包装在一个回调函数中, 并将此回调函数传入到scheduler
包等待调度。
- 装载渲染器, 渲染器必须实现
- 渲染器
react-dom
包, 有2
个核心职责:- 引导
react
应用的启动(通过ReactDOM.render
)。 - 实现
HostConfig
协议(源码在ReactDOMHostConfig.js
中), 能够将react-reconciler
包构造出来的fiber
树表现出来, 生成dom
节点(浏览器中), 生成字符串(ssr
)。
- 引导
react 中 有个重要的东西叫 workerloop scheduler和react-reconciler
区别
- 任务调度循环是以二叉堆为数据结构(详见react 算法之堆排序), 循环执行堆的顶点, 直到堆被清空.
- 任务调度循环的逻辑偏向宏观, 它调度的是每一个任务(task), 而不关心这个任务具体是干什么的(甚至可以将Scheduler包脱离react使用), 具体任务其实就是执行回调函数performSyncWorkOnRoot或performConcurrentWorkOnRoot.
- fiber构造循环是以树为数据结构, 从上至下执行深度优先遍历(详见react 算法之深度优先遍历).
- fiber构造循环的逻辑偏向具体实现, 它只是任务(task)的一部分(如performSyncWorkOnRoot包括: fiber树的构造, DOM渲染, 调度检测), 只负责fiber树的构造.
联系
fiber构造循环是任务调度循环中的任务(task)的一部分. 它们是从属关系, 每个任务都会重新构造一个fiber树.
优化点
scheduler包中调度原理, 也就是React两大工作循环中的任务调度循环. 并介绍了时间切片和可中断渲染等特性在任务调度循环中的实现. scheduler包是React运行时的心脏, 为了提升调度性能, 注册task之前, 在react-reconciler包中做了节流和防抖等措施.
主要逻辑
- 输入: 将每一次更新(如: 新增, 删除, 修改节点之后)视为一次更新需求(目的是要更新DOM节点).
- 注册调度任务: react-reconciler收到更新需求之后, 并不会立即构造fiber树, 而是去调度中心scheduler注册一个新任务task, 即把更新需求转换成一个task.
- 执行调度任务(输出): 调度中心scheduler通过任务调度循环来执行task(task的执行过程又回到了react-reconciler包中).
- fiber构造循环是task的实现环节之一, 循环完成之后会构造出最新的 fiber 树.
- commitRoot是task的实现环节之二, 把最新的 fiber 树最终渲染到页面上, task完成.
优先级使用
就是 可中断渲染,时间切片(time slicing),异步渲染(suspense)等特性
React Context 原理
Context
提供了一种直接访问祖先节点上的状态的方法, 避免了多级组件层层传递props
,而提高时间复杂度浪费性能。
- 在消费状态时,
ContextConsumer
节点调用readContext(MyContext)
获取最新状态. - 在更新状态时, 由
ContextProvider
节点负责查找所有ContextConsumer
节点, 并设置消费节点的父路径上所有节点的fiber.childLanes
, 保证消费节点可以得到更新.
useState
useState
和 useReducer
原理基本一致 都属于状态类的hooks 只不过调用的方法不同,
分别是 mountState 和 mountReducer
mountState和mountReducer逻辑简单: 主要负责创建hook, 初始化hook的属性, 最后返回[当前状态, dispatch函数]
唯一的不同点是 hook.queue.lastRenderedReducer
,mountReducer使用的是外部传入自定义reducer
- 原理就是,在useState(initialState)函数内部, 设置hook.memoizedState = hook.baseState = initialState;, 初始状态被同时保存到了hook.baseState, hook.memoizedState中
- hook.memoizedState: 当前状态
- hook.baseState: 基础状态, 作为合并hook.baseQueue的初始值
- 最后返回[hook.memoizedState, dispatch], 所以在function中使用的是hook.memoizedState.
合成事件
从架构上来讲, SyntheticEvent打通了从外部原生事件到内部fiber树的交互渠道, 使得react能够感知到浏览器提供的原生事件, 进而做出不同的响应, 修改fiber树, 变更视图等. 从实现上讲, 主要分为 3 步:
- 监听原生事件: 对齐DOM元素和fiber元素
- 收集listeners: 遍历fiber树, 收集所有监听本事件的listener函数.
- 派发合成事件: 构造合成事件, 遍历listeners进行派发.